Objective-C中的装饰模式

前段时间接触到了装饰模式,也做了基本的了解,但是还不是很清楚它在iOS开发中的实际运用,和合理的存在方式。这两天看了《Objective-C编程之道:iOS设计模式解析》 中的第16章,有了更加深的理解,结合自己的理解在这里做一下记录。

什么是装饰模式

就描述概念而言,我觉得《Head First 设计模式》这本书通过各种例子阐述得要更加详细易懂。《Objective-C编程之道:iOS设计模式解析》讲得更多是iOS开发中的使用和不同实现方式的对比。

标准的装饰模式有包括一个抽象的Component父类,它声明了一些操作,它具体的类讲进行重载以实现自己特定的操作。这个Component具体类是模式中的被装饰者,Component父类可以被细化为另一个叫做Decorator的抽象类,即装饰者抽象类。Decorator类中包含了一个Component的引用。Decorator的具体类为Component或者Decorator定义了几个扩展行为,并且会在自己的操作中内嵌Component操作。关系图见 装饰模式类图

Component定义了一些抽象操作,具体类将进行重载实现自己特定的操作。Decorator抽象类通过将一个Component(或Decorator)内嵌到Decorator对象,定义了扩展这个Component的实例的“装饰性”的行为。

默认的operation方法只是想内嵌的Component发送一个消息,Decorator的具体实现类重载父类的operation,通过super把自己增加的行为扩展给Component的operation。如果只需要向Component添加一种职责,那可以省掉抽象的Decorator类,让具体的Decorator直接把请求转发给Component。那么这种方式就好像形成一种操作链,把一种行为加到另一种行为之上,如下图中的对象图

装饰模式类图

何时使用装饰模式

  • 想要在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

  • 想要扩展一个类的行为,却做不到。类定义可能被隐藏,无法进行子类化;或者对类的每个行为的扩展,为支持每种功能组合,将产生大量的子类

  • 对类的职责的扩展是可选的。

装饰模式在iOS中的实现

根据Objective-C的特性,有两种实现方式:

  1. 通过真正的子类实现装饰
  2. 通过分类实现装饰

第二种方式是使用了Objective-C的语言功能,通过分类向类添加行为,不必进行子类化,这并非标准的装饰模式结构,但是实现了装饰模式同样的需求。尽管使用分类来实现装饰模式跟原始风格有偏离,但是实现少量的装饰器的时候,它比真正子类方式更加轻量、更加容易。

例子是使用装饰实现UIImage的滤镜功能 终于要见代码了~


真子类实现装饰

先看目录结构:


Component抽象类是一个Protocol文件,


@protocol ImageComponent <NSObject>
@optional
- (void)drawAsPatternInRect:(CGRect)rect;
- (void)drawAtPoint:(CGPoint)point;
- (void)drawAtPoint:(CGPoint)ponit blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;
- (void)drawInRect:(CGRect)rect;
- (void)drawInRect:(CGRect)rect blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;
@end

UIImage_ImageComponent.h是UIImage的一个Extension,让UIImage遵循ImageComponent协议。如此在装饰模式中UIImage类是ImageComponent抽象类的具体类,即UIImage是模式中的被装饰者。

Decorator抽象类ImageFilter遵守ImageComponent协议,并定义了三个方法和一个ImageComponent协议的代理对象属性。


@interface ImageFilter : NSObject <ImageComponent>
- (void)apply;
- (id)initWithImageComponent:(id <ImageComponent>) component;
- (id)forwardingTargetForSelector:(SEL)aSelector;
- 
@end


@interface ImageFilter ()
@property (nonatomic, strong) id <ImageComponent> component;
@end
@implementation ImageFilter

- (instancetype)initWithImageComponent:(id<ImageComponent>)component {
    if (self = [super init]) {
        [self setComponent:component];
    }
    return self;
}
- (void)apply {
    //由子类重载
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSString *selectorName = NSStringFromSelector(aSelector);
    if ([selectorName hasPrefix:@"draw"]) {
        [self apply];
    }
    return self.component;
}

@end

在Decorator抽象类的具体实现类中去重载apply方法,当发送以"draw"开头的消息时,先执行Decorator具体类中的apply方法,然后再执行Component具体类即UIImage类(被装饰者)中的"draw"开头的那个方法。如此实现了对UIImage对应的"draw"开头方法动态添加行为。

具体的使用代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    UIImage *image = [UIImage imageNamed:@"avatar.jpg"];
    CGAffineTransform rorateTransform = CGAffineTransformMakeRotation(-M_PI / 4);
    CGAffineTransform translateTransform = CGAffineTransformMakeTranslation(-image.size.width / 2.0, image.size.height / 8.0);
    CGAffineTransform finalTransform = CGAffineTransformConcat(rorateTransform, translateTransform);
    
    id<ImageComponent>transformdImage = [[ImageTransformFilter alloc] initWithImageComponent:image transform:finalTransform];
    
    id<ImageComponent>finalImage = [[ImageShadowFliter alloc] initWithImageComponent:transformdImage];
    
    DecoratorView *decoratorView = [[DecoratorView alloc] initWithFrame:self.view.frame];
    [decoratorView setImage:finalImage];
    [self.view addSubview:decoratorView];
}

DecoratorView中的drawRect:调起UIImage的装饰

@implementation DecoratorView
during animation.
- (void)drawRect:(CGRect)rect {
    [self.image drawInRect:rect];
}
@end

类图如下:

各种ImageComponent对象可在运行时进行连接,如下图:



分类实现装饰

分类方式实现就是平常开发中常见的使用,代码就不再一一列出

类图如下:


BaseFilter分类中定义了绘图的方法,Transformshadow分类中可以调用BaseFilter分类中定义的方法来进行自己的绘制。他们也可以像真正子类化方式那样链接起来,来看对象图:


总结

装饰模式在Objective-C中有两种不同的实现方式,真正子类方式的实现使用一种较为结构化的方式链接各种装饰器,分类的方式更加简单和轻量,使用于现有类只需要少量装饰器的应用。


参考:《Objective-C编程之道:iOS设计模式解析》第16章,(190-206)
demo地址:GitHub

顺手点个喜欢是对我最大支持!
欢迎关注我的博客👉冷读空间

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容

  • 1 场景问题# 1.1 复杂的奖金计算## 考虑这样一个实际应用:就是如何实现灵活的奖金计算。 奖金计算是相对复杂...
    七寸知架构阅读 3,802评论 4 66
  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,820评论 1 15
  • 一、设计模式的分类 总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者...
    lichengjin阅读 861评论 0 8
  • 1 概述 在一个项目中,你会有非常多的因素考虑不到,特别是业务的变更,不时的冒出一个需求是很正常的情况。有三个继承...
    今晚打肉山阅读 284评论 0 0
  • 目录 | 上一章 如梦 窗外已有鸟鸣。 巷子里的石板路已经湿透,两边的屋檐上正断续往下滴水。远处,卖花的小姑娘提着...
    暝色入高楼阅读 377评论 7 3